package com.cloudree.gllib.shape;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.support.annotation.DrawableRes;
import android.support.annotation.RawRes;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;

/**
 * Created by Administrator on 2017/8/1.
 */
public abstract class Shape {
    public static final int BYTE_PER_FLOAT = 4;
    protected int mProgram;
    protected FloatBuffer vertexBuffer;
    protected FloatBuffer colorBuffer;


    public Shape() {
        loadingMaterial();
        compileShader();
    }

    /**
     * loading vertex data,color data or light data
     */
    public abstract void loadingMaterial();

    /**
     * compile data then be used by gl program
     */
    public abstract void compileShader();


    /**
     * draw picture
     */
    public abstract void draw(float[] mvpMatrix, float[] modelMatrix, float[] viewMatrix, float[] projectionMatrix);


    /**
     * vertex shader code
     */
    public abstract String getVertexShaderCode();

    /**
     * fragment shader code
     */
    public abstract String getFragmentShaderCode();

    /**
     * generate shader
     * GLES20.GL_FRAGMENT_SHADER
     * GLES20.GL_VERTEX_SHADER
     */
    protected int generateShader(int shaderType, String shaderCode) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, shaderCode);
            GLES20.glCompileShader(shader);
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            //if compile failed then delete it
            if (compileStatus[0] == 0) {
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        if (shader == 0) {
            throw new RuntimeException("Error create gl shader.");
        }
        return shader;
    }

    /**
     * generate gl program
     */
    protected int generateGlProgram(int vertexShader, int fragmentShader, String... attrs) {
        int program = GLES20.glCreateProgram();
        if (program != 0) {
            //attach shader
            GLES20.glAttachShader(program, vertexShader);
            GLES20.glAttachShader(program, fragmentShader);
            //bind attributes
            for (int i = 0; i < attrs.length; i++) {
                GLES20.glBindAttribLocation(program, i, attrs[i]);
            }

            //get link status
            final int[] linkStatus = new int[1];
            GLES20.glLinkProgram(program);
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            //check status
            if (linkStatus[0] == 0) {
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        if (program == 0) {
            throw new RuntimeException("Error create program");
        }
        return program;
    }

    /**
     * load texture  mapping
     */
    protected int loadTexture(Context context, @DrawableRes int resId) {
        GLES20.glEnable(GLES20.GL_TEXTURE_2D);
        int[] texture = new int[1];
        GLES20.glGenTextures(1, texture, 0);
        if (texture[0] != 0) {
            //loading resource
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;
            Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);

            if (bitmap == null) {
                throw new RuntimeException(resId + "can`t be decoded into a texture");
            }

            //bind bitmap
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

            GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);

            bitmap.recycle();

        } else {
            throw new RuntimeException("Error load texture");
        }

        return texture[0];
    }

    /**
     * read raw files
     */
    public String readRawFile(Context context, @RawRes int resId) {
        InputStream is = context.getResources().openRawResource(resId);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String str = "";
        StringBuffer sb = new StringBuffer();
        try {
            while ((str = br.readLine()) != null) {
                sb.append(str);
                sb.append("\n");
            }

        } catch (IOException e) {
            return null;
        }
        return sb.toString();
    }


    /**
     * generate cube
     */
    public float[] generateCubeData(float[] point1,
                                    float[] point2,
                                    float[] point3,
                                    float[] point4,
                                    float[] point5,
                                    float[] point6,
                                    float[] point7,
                                    float[] point8,
                                    int elementsPerPoint) {
        // Given a cube with the points defined as follows:
        // front left top, front right top, front left bottom, front right bottom,
        // back left top, back right top, back left bottom, back right bottom,
        // return an array of 6 sides, 2 triangles per side, 3 vertices per triangle, and 4 floats per vertex.
        final int FRONT = 0;
        final int RIGHT = 1;
        final int BACK = 2;
        final int LEFT = 3;
        final int TOP = 4;
        final int BOTTOM = 5;

        final int size = elementsPerPoint * 6 * 6;
        final float[] cubeData = new float[size];

        for (int face = 0; face < 6; face++) {
            // Relative to the side, p1 = top left, p2 = top right, p3 = bottom left, p4 = bottom right
            final float[] p1, p2, p3, p4;

            // Select the points for this face
            if (face == FRONT) {
                p1 = point1;
                p2 = point2;
                p3 = point3;
                p4 = point4;
            } else if (face == RIGHT) {
                p1 = point2;
                p2 = point6;
                p3 = point4;
                p4 = point8;
            } else if (face == BACK) {
                p1 = point6;
                p2 = point5;
                p3 = point8;
                p4 = point7;
            } else if (face == LEFT) {
                p1 = point5;
                p2 = point1;
                p3 = point7;
                p4 = point3;
            } else if (face == TOP) {
                p1 = point5;
                p2 = point6;
                p3 = point1;
                p4 = point2;
            } else // if (side == BOTTOM)
            {
                p1 = point8;
                p2 = point7;
                p3 = point4;
                p4 = point3;
            }

            // In OpenGL counter-clockwise winding is default. This means that when we look at a triangle,
            // if the points are counter-clockwise we are looking at the "front". If not we are looking at
            // the back. OpenGL has an optimization where all back-facing triangles are culled, since they
            // usually represent the backside of an object and aren't visible anyways.

            // Build the triangles
            //  1---3,6
            //  | / |
            // 2,4--5
            int offset = face * elementsPerPoint * 6;

            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p1[i];
            }
            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p3[i];
            }
            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p2[i];
            }
            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p3[i];
            }
            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p4[i];
            }
            for (int i = 0; i < elementsPerPoint; i++) {
                cubeData[offset++] = p2[i];
            }
        }

        return cubeData;
    }


}
